﻿using System.Collections.Generic;
using System.Linq;
using System.Text;
using RiaServicesContrib;

namespace EntityGraph.SQL
{
    internal class EntityGraphPathCollection : IEnumerable<EntityGraphPath>
    {
        private List<EntityGraphPath> Paths { get; set; }

        public EntityGraphPathCollection(EntityGraphShape shape)
        {
            Paths = new List<EntityGraphPath>();
            BuildPaths(shape);
            // Disabled see remarks at method FilterTransitivePaths
            // FilterTransitivePaths();
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach(var p in this)
            {
                sb.AppendLine(p.ToString());
            }
            return sb.ToString();
        }
        private EntityGraphPath Edge2Path(EntityGraphEdge edge)
        {
            var path = new EntityGraphPath{
                        new EntityGraphPathNode 
                        {
                            InEdge = null,
                            Type = edge.FromType,
                            OutEdge = edge,
                        },
                        new EntityGraphPathNode{
                            InEdge=edge,
                            Type=edge.ToType,
                            OutEdge=null}
                    };
            if(edge.EdgeInfo.DeclaringType != edge.FromType)
            {
                var node = new EntityGraphPathNode
                {
                    InEdge = null,
                    OutEdge = path[0].OutEdge,
                    Type = edge.EdgeInfo.DeclaringType
                };
                path.Insert(0, node);
            }
            return path;
        }
        private void BuildPaths(EntityGraphShape shape)
        {
            foreach(var edge in shape)
            {
                var path = Edge2Path(edge);
                Paths.Add(path);
            }
            bool progress = false;
            do
            {
                progress = false;
                foreach(var edge in shape)
                {
                    // Create paths accroding to a transitivity relations a->b, c->d ==> a->c->d iff c is assignable from b
                    foreach(var path in Paths.Where(p => edge.ToType.IsAssignableFrom(p.First().Type)))
                    {
                        var node = path.First();
                        if(node.Type != edge.ToType)
                        {
                            node = new EntityGraphPathNode { InEdge = null, OutEdge = null, Type = edge.ToType };
                            path.Insert(0, node);
                        }
                        node.InEdge = edge;
                        path.Insert(0, new EntityGraphPathNode { InEdge = null, OutEdge = edge, Type = edge.FromType });

                        progress = true;
                    }
                }
            } while(progress == true);

        }
        public IEnumerator<EntityGraphPath> GetEnumerator()
        {
            return Paths.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

#if FilterTransitivePathsEnabled
        // The idea is that using transitivity we can reduce the number of paths as follows:
        // If a path A -> B -> C exists, then
        // a path A -> B -> X .. -> C can be removed
        // The idea is the the collection of Cs in the first path is always a superset of the
        // Cs resulting from the second path. Now, this is only true iff C holds the id of B (and _not_
        // the other way around. That is, iff B has a many-2-one relation to C (C holds many Bs).
        // This heuristic seems not to be valid.
        private void FilterTransitivePaths()
        {
            foreach(var path in Paths.ToList())
            {
                if(ShortestPath(path) != path)
                {
                    Paths.Remove(path);
                }
            }
        }
        /// <summary>
        /// Returns true if the current path starts with path.
        /// </summary>
        /// <param name="path1"></param>
        /// <param name="path2"></param>
        /// <returns></returns>
        public bool StartsWith(EntityGraphPath path1, EntityGraphPath path2)
        {
            var count = path2.Count();
            if(path1.Count() <= count)
            {
                return false;
            }
            for(int i = 0; i + 1 < count; i++)
            {
                if(path1[i].Type.IsAssignableFrom(path2[i].Type) == false)
                {
                    return false;
                }
            }
            return true;
        }

        private EntityGraphPath ShortestPath(EntityGraphPath path)
        {
            var found = path;
            foreach(var p in Paths.Where(p => p.Count() < path.Count() && p.Last().Type.IsAssignableFrom(path.Last().Type)))
            {
                if(StartsWith(path, p))
                {
                    if(p.Count() < found.Count())
                    {
                        found = p;
                    }
                }
            }
            return found;
        }
#endif
    }
}
